/*
 * pilua_proto.c
 *
 * Plugin interface to the Lua Programming Language
 *
 * (c) 2024, Lau
 *
 */

#include <stdint.h>
#include <stdbool.h>
#include <glib.h>

#include "wmem_strbuf.h"

#include "tvbuff.h"
#include "tvbuff-int.h"
#include "packet.h"
#include "ftypes.h"
#include "proto.h"
#include "value_string.h"
#include "true_false_strings.h"
#include "unit_strings.h"

#include "pilua.h"

#include "strutil.h"

/* PILUA_MODULE Proto Functions For New Protocols And Dissectors

   The classes and functions in this chapter allow Lua scripts to create new protocols for Wireshark.
    <<lua_class_Proto,`Proto`>> protocol objects can have <<lua_class_Pref,`Pref`>> preferences, <<lua_class_ProtoField,`ProtoField`>> fields for filterable values that can be displayed in a details view tree, functions for dissecting the new protocol, and so on.

   The dissection function can be hooked into existing protocol tables through <<lua_class_DissectorTable,`DissectorTable`>> so that the new protocol dissector function gets called by that protocol, and the new dissector can itself call on other, already existing protocol dissectors by retrieving and calling the <<lua_class_Dissector,`Dissector`>> object.
   A <<lua_class_Proto,`Proto`>> dissector can also be used as a post-dissector, at the end of every frame's dissection, or as a heuristic dissector.
*/


/*
 * _func_saver stores function refs so that Lua won't garbage collect them prematurely.
 * It is only used by tcp_dissect_pdus right now.
 */
typedef struct _func_saver {
    lua_State* state;
    int get_len_ref;
    int dissect_ref;
} func_saver_t;

static GPtrArray* outstanding_FuncSavers;

void clear_outstanding_FuncSavers(void)
{
    while (outstanding_FuncSavers->len) {
        func_saver_t* fs = (func_saver_t*)g_ptr_array_remove_index_fast(outstanding_FuncSavers,0);
        if (fs->state) {
            lua_State* L = fs->state;
            if (fs->get_len_ref != LUA_NOREF) {
                luaL_unref(L, LUA_REGISTRYINDEX, fs->get_len_ref);
            }
            if (fs->dissect_ref != LUA_NOREF) {
                luaL_unref(L, LUA_REGISTRYINDEX, fs->dissect_ref);
            }
        }
        free(fs);
    }
}


PILUA_CLASS_DEFINE(Proto, FAIL_ON_NULL("Proto"));
/*
  A new protocol.
  Protocols have several uses.
  The main one is to dissect a protocol, but they can also be dummies used to register preferences for other purposes.
 */

static int protocols_table_ref = LUA_NOREF;

PILUA_CONSTRUCTOR Proto_new(lua_State* L)
{
    /* Creates a new <<lua_class_Proto,`Proto`>> object. */
#define PILUA_ARG_Proto_new_NAME            1 /* The name of the protocol. */
#define PILUA_ARG_Proto_new_DESCRIPTION     2 /* A Long Text description of the protocol (usually lowercase). */
    const char* name = luaL_checkstring(L, PILUA_ARG_Proto_new_NAME);
    const char* desc = luaL_checkstring(L, PILUA_ARG_Proto_new_DESCRIPTION);
    Proto proto;
    char *loname, *hiname;

    /* TODO: should really make a common function for all of pilua that does checkstring and non-empty at same time */
    if (!name[0]) {
        PILUA_ARG_ERROR(Proto_new, NAME, "must not be an empty string");
        return 0;
    }

    if (!desc[0]) {
        PILUA_ARG_ERROR(Proto_new, DESCRIPTION, "must not be an empty string");
        return 0;
    }

    if (proto_name_already_registered(desc)) {
        PILUA_ARG_ERROR(Proto_new, DESCRIPTION, "there cannot be two protocols with the same description");
        return 0;
    }

    loname = ascii_tolower(name);
    if (proto_check_field_name(loname)) {
        free(loname);
        PILUA_ARG_ERROR(Proto_new, NAME, "invalid character in name");
        return 0;
    }

    hiname = ascii_toupper(name);
    if ((proto_get_id_by_short_name(hiname) != -1) ||
        (proto_get_id_by_filter_name(loname) != -1))
    {
        free(loname);
        free(hiname);
        PILUA_ARG_ERROR(Proto_new, NAME, "there cannot be two protocols with the same name");
        return 0;
    }

    proto = calloc(1, sizeof(pilua_proto_t));

    proto->name = hiname;
    proto->loname = loname;
    proto->desc = strdup(desc);
    proto->hfid = proto_register_protocol(proto->desc, hiname, loname);
    proto->ett = -1;
    proto->is_postdissector = false;
    proto->expired = false;

    lua_newtable (L);
    proto->fields = luaL_ref(L, LUA_REGISTRYINDEX);

//    lua_newtable (L);
//    proto->expert_info_table_ref = luaL_ref(L, LUA_REGISTRYINDEX);
//    proto->expert_module = expert_register_protocol(proto->hfid);

//    proto->prefs.name = NULL;
//    proto->prefs.label = NULL;
//    proto->prefs.desc = NULL;
//    proto->prefs.value.u = 0;
//    proto->prefs.next = NULL;
//    proto->prefs.proto = proto;

//    proto->prefs_module = NULL;
    proto->handle = NULL;

    lua_rawgeti(L, LUA_REGISTRYINDEX, protocols_table_ref);

    lua_pushstring(L, loname);
    pushProto(L, proto);

    lua_settable(L, -3);

    pushProto(L, proto);

    PILUA_RETURN(1); /* The newly created <<lua_class_Proto,`Proto`>> object. */
}

PILUA_METAMETHOD Proto__call(lua_State* L)
{
    /* Creates a <<lua_class_Proto,`Proto`>> object. */
#define PILUA_ARG_Proto__call_NAME          1 /* The name of the protocol. */
#define PILUA_ARG_Proto__call_DESCRIPTION   2 /* A Long Text description of the protocol (usually lowercase). */
    lua_remove(L, 1); /* remove the table */

    PILUA_RETURN(Proto_new(L)); /* The new <<lua_class_Proto,`Proto`>> object. */
}

static int Proto__tostring(lua_State* L)
{
    Proto proto = checkProto(L, 1);

    if (proto) {
        lua_pushfstring(L, "Proto: expired=%s, name=%s, loname=%s, desc=%s, id=%d.",
                        proto->expired ? "true" : "false",
                        proto->name,
                        proto->loname,
                        proto->desc,
                        proto->hfid);
    } else {
        lua_pushfstring(L, "No Proto object!");
    }

    return 1;
}

PILUA_FUNCTION pilua_register_postdissector(lua_State* L)
{
    /* Make a <<lua_class_Proto,`Proto`>> protocol (with a dissector function) a post-dissector.
       It will be called for every frame after dissection. */
#define PILUA_ARG_register_postdissector_PROTO          1 /* The protocol to be used as post-dissector. */
#define PILUA_OPTARG_register_postdissector_ALLFIELDS   2 /* Whether to generate all fields.
                                                           Note: This impacts performance (default=false). */

    Proto proto = checkProto(L, PILUA_ARG_register_postdissector_PROTO);
    const bool all_fields = pilua_optbool(L, PILUA_OPTARG_register_postdissector_ALLFIELDS, false);

    if (!proto->is_postdissector) {
//        if (! proto->handle) {
//            proto->handle = register_dissector(proto->loname, dissect_lua, proto->hfid);
//        }

//        register_postdissector(proto->handle);
        proto->is_postdissector = true;
    } else {
        luaL_argerror(L, 1, "this protocol is already registered as postdissector");
    }

//    if (all_fields) {
//        /*
//         * XXX - are there any Lua postdissectors that need "all fields",
//         * i.e. the entire protocol tree, or do they just look for
//         * *particular* fields, with field extractors?
//         *
//         * And do all of them require the actual *displayed* format of
//         * the fields they need?
//         *
//         * If not, this is overkill.
//         */
//        epan_set_always_visible(true);
//    }

    return 0;
}

/* PILUA_ATTRIBUTE Proto_dissector RW The protocol's dissector, a function you define.

   When later called, the function will be given:
       1. A <<lua_class_Tvb,`Tvb`>> object
       2. A <<lua_class_Pinfo,`Pinfo`>> object
       3. A <<lua_class_TreeItem,`TreeItem`>> object
*/
static int Proto_get_dissector(lua_State* L)
{
    Proto proto = checkProto(L, 1);

    if (proto->handle) {
        pushDissector(L, proto->handle);
        return 1;
    }

    luaL_error(L, "The protocol hasn't been registered yet");
    return 0;
}

static int Proto_set_dissector(lua_State* L)
{
    Proto proto = checkProto(L, 1);

    if (lua_isfunction(L, 2)) {
        /* insert the dissector into the dissectors table */
        lua_rawgeti(L, LUA_REGISTRYINDEX, lua_dissectors_table_ref);
        lua_replace(L, 1);
        lua_pushstring(L, proto->name);
        lua_insert(L, 2); /* function is now at 3 */
        lua_settable(L, 1);

        if (! proto->handle) {
            proto->handle = register_dissector(proto->loname, dissect_lua, proto->hfid);
        }
    } else {
        luaL_argerror(L, 2, "The dissector of a protocol must be a function");
    }

    return 0;
}

/* PILUA_ATTRIBUTE Proto_init WO The init routine of this dissector, a function you define.

   The init function is called when the a new capture file is opened or when
   the open capture file is closed.  It is passed no arguments.
*/
static int Proto_set_init(lua_State* L)
{
    Proto proto = checkProto(L, 1);

    if (lua_isfunction(L, 2)) {
        /* insert the init routine into the init_routines table */
        lua_getglobal(L, PILUA_INIT_ROUTINES);
        lua_replace(L, 1);
        lua_pushstring(L, proto->name);
        lua_insert(L, 2); /* function is now at 3 */
        lua_settable(L, 1);
    }  else {
        luaL_argerror(L, 2, "The initializer of a protocol must be a function");
    }
    return 0;
}

/* PILUA_ATTRIBUTE Proto_enable RW Set/get <<lua_class_Proto,`Proto`>>'s enable state (boolean). */
static int Proto_get_enable(lua_State* L)
{
    Proto proto = checkProto(L, 1);

    lua_pushboolean(L, proto_is_protocol_enabled(find_protocol_by_id(proto->hfid)));

    return 1;
}

/* the following is used as both a method and attribute. As a method it defaults
   to setting the value, because that's what it used to do before. */
PILUA_METHOD Proto_set_enable(lua_State *L)
{
    /*
    Marks the <<lua_class_Proto,`Proto`>> as a hidden field (neither displayed nor used in filters).
    Deprecated

    This used to return nothing, but as of 1.11.3 it returns the same tree item to allow chained calls.
    */
#define PILUA_OPTARG_Proto_set_enable_BOOL 2 /* A Lua boolean, which if `true` sets the <<lua_class_Proto,`Proto`>>
                                                      enable flag, else clears it. Default is `true`. */
    Proto proto = checkProto(L, 1);
    bool set = pilua_optbool(L, PILUA_OPTARG_Proto_set_enable_BOOL, true);

    if (set) {
        proto_set_decoding(proto->hfid, proto->enabled);
    } else {
        proto_set_decoding(proto->hfid, proto->enabled);
    }

    /* copy the Proto userdata so we give it back */
    lua_pushvalue(L, 1);

    PILUA_RETURN(1); /* The same Proto. */
}

/* PILUA_ATTRIBUTE Proto_name RO The name given to this dissector. */
PILUA_ATTRIBUTE_STRING_GETTER(Proto, name);

/* PILUA_ATTRIBUTE Proto_description RO The description given to this dissector. */
PILUA_ATTRIBUTE_NAMED_STRING_GETTER(Proto, description, desc);

/* PILUA_ATTRIBUTE Proto_fields RW The `ProtoField`++'++s Lua table of this dissector. */
static int Proto_get_fields(lua_State* L)
{
    Proto proto = checkProto(L, 1);
    lua_rawgeti(L, LUA_REGISTRYINDEX, proto->fields);
    return 1;
}

static bool Proto_append_ProtoField(Proto proto, ProtoField f)
{
    if (f->hfid != -2) {
        // Already registered
        return false;
    }
    hf_register_info hfri = { NULL, { NULL, NULL, FT_NONE, 0, NULL, 0, NULL, HFILL } };
    int*   ettp = NULL;
    ettp = &(f->ett);

    hfri.p_id = &(f->hfid);
    hfri.hfinfo.name = f->name;
    hfri.hfinfo.abbrev = f->abbrev;
    hfri.hfinfo.type = f->type;
    hfri.hfinfo.display = f->base;
    hfri.hfinfo.strings = VALS(f->vs);
    hfri.hfinfo.bitmask = f->mask;
    hfri.hfinfo.blurb = f->blob;

    f->hfid = -1;
    g_array_append_val(proto->hfa, hfri);
//    g_array_append_val(proto->etta, ettp);

    return true;
}

static int Proto_set_fields(lua_State* L)
{
    Proto proto = checkProto(L, 1);
#define FIELDS_TABLE    2
#define NEW_TABLE       3
#define NEW_FIELD       3

    /*
     * XXX - This is a "setter", but it really appends any ProtoFields to
     * the Lua Table without removing any existing ones.
     */

    if (proto->hfa) {
        /* This Proto's ProtoFields were already registered in Proto_commit.
         * Deregister the existing array with epan so we can add new ones.
         * (Appending to the GArray and registering only the new ones would
         * have a use-after-free, for reasons mentioned in proto.c )
         * XXX - What is the reason for waiting and registering all
         * at once in Proto_commit instead of doing it here every time?
         */
        if (proto->hfa->len) {
            proto_add_deregistered_data(g_array_free(proto->hfa, false));
        } else {
            g_array_free(proto->hfa, true);
        }
        /* No need for deferred deletion of subtree indexes */
//        g_array_free(proto->etta, true);
        proto->hfa  = g_array_new(true, true, sizeof(hf_register_info));
//        proto->etta = g_array_new(true, true, sizeof(int*));
    }

    lua_rawgeti(L, LUA_REGISTRYINDEX, proto->fields);
    lua_insert(L, FIELDS_TABLE);

    if( lua_istable(L, NEW_TABLE)) {
        for (lua_pushnil(L); lua_next(L, NEW_TABLE); ) {
            if (isProtoField(L, 5)) {
                if (proto->hfa) {
                    ProtoField f = toProtoField(L, 5);
                    // XXX this will leak resources on error
                    // If this continued and registered the field array, it
                    // wouldn't leak. We could perhaps print a warning or even
                    // err after registration.
                    if (!Proto_append_ProtoField(proto, f)) {
                        return luaL_error(L, "%s is already registered; fields can be registered only once", f->abbrev);
                    }
                }
                /* luaL_ref returns a reference. lua_next will return not
                 * just occupied entries in the table, but also references
                 * used to store unused/deleted entries in the hash table
                 * so that they can be reused without reallocation. Those
                 * will have a lua_Number as their value. The values form
                 * a linked list of available indicies, starting with the
                 * head at index 3 (LUA_RIDX_LAST + 1) in Lua 5.4 and index
                 * 0 in earlier versions. (Since arrays are 1-indexed, this
                 * is mostly invisible in Lua 5.3 and earlier so long as
                 * nothing has been deleted.)
                 *
                 * Perhaps the assumption is that no one wants to use a
                 * hash table to store numbers anyway? This also means
                 * that for any table with 2 or more real entries, the
                 * length operator # *includes* the freelist and cannot
                 * be trusted.
                 *
                 * If we wanted to only check entries we knew were valid,
                 * we could save this reference.
                 *
                 * This also means that our checks below on registration
                 * and deregistration that the table entries are ProtoFields
                 * are less useful, because we do now expect some numbers
                 * in the table. Hopefully the check on insert here obviates
                 * needing to check there.
                 */
                /* int ref = */ luaL_ref(L, FIELDS_TABLE);
            } else if (! lua_isnil(L, 5) ) {
                return luaL_error(L, "only ProtoFields should be in the table");
            }
        }
    } else if (isProtoField(L, NEW_FIELD)){
        if (proto->hfa) {
            ProtoField f = toProtoField(L, NEW_FIELD);
            // XXX this will leak resources on error
            // If this continued and registered the field array, it wouldn't
            // leak. We could perhaps print a warning or even err after
            // registration.
            if (!Proto_append_ProtoField(proto, f)) {
                return luaL_error(L, "%s is already registered; fields can be registered only once", f->abbrev);
            }
        }
        lua_pushvalue(L, NEW_FIELD);
        luaL_ref(L, FIELDS_TABLE);

    } else {
        return luaL_error(L, "either a ProtoField or an array of protofields");
    }

    if (proto->hfa && proto->hfa->len) {
        /* register the proto fields */
        proto_register_field_array(proto->hfid,&g_array_index(proto->hfa, hf_register_info, 0),proto->hfa->len);
//        proto_register_subtree_array(&g_array_index(proto->etta, int*, 0),proto->etta->len);
    }

    lua_pushvalue(L, 3);

    return 1;
}

/* Gets registered as metamethod automatically by PILUA_REGISTER_CLASS/META */
static int Proto__gc(lua_State* L)
{
    /* Proto is registered twice, once in protocols_table_ref and once returned from Proto_new.
     * It will not be freed unless deregistered.
     */
    Proto proto = toProto(L, 1);

    if (!proto->expired) {
        proto->expired = true;
    } else if (proto->hfid == -2) {
        /* Only free deregistered Proto */
        free(proto);
    }

    return 0;
}

/* This table is ultimately registered as a sub-table of the class' metatable,
 * and if __index/__newindex is invoked then it calls the appropriate function
 * from this table for getting/setting the members.
 */
PILUA_ATTRIBUTES Proto_attributes[] = {
    PILUA_ATTRIBUTE_RWREG(Proto, dissector),
    PILUA_ATTRIBUTE_RWREG(Proto, fields),
    PILUA_ATTRIBUTE_WOREG(Proto, init),
    PILUA_ATTRIBUTE_ROREG(Proto, name),
    PILUA_ATTRIBUTE_RWREG(Proto, enable),
    PILUA_ATTRIBUTE_ROREG(Proto, description),
    { NULL, NULL, NULL }
};

PILUA_METHODS Proto_methods[] = {
    PILUA_CLASS_FNREG(Proto, new),
    { NULL, NULL }
};

PILUA_META Proto_meta[] = {
    PILUA_CLASS_MTREG(Proto, tostring),
    PILUA_CLASS_MTREG(Proto, call),
    { NULL, NULL }
};

int Proto_register(lua_State* L)
{
    PILUA_REGISTER_CLASS_WITH_ATTRS(Proto);

    if (outstanding_FuncSavers != NULL) {
        g_ptr_array_unref(outstanding_FuncSavers);
    }
    outstanding_FuncSavers = g_ptr_array_new();

    lua_newtable(L);
    protocols_table_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    return 0;
}

/**
 * Query field abbr that is defined and bound to a Proto in lua.
 * They are not registered until the end of the initialization.
 */
ProtoField pilua_is_field_available(lua_State* L, const char* field_abbr)
{
    lua_rawgeti(L, LUA_REGISTRYINDEX, protocols_table_ref);
    lua_pushnil(L);
    while (lua_next(L, -2)) {
        Proto proto;
        proto = checkProto(L, -1);

        lua_rawgeti(L, LUA_REGISTRYINDEX, proto->fields);

        lua_pushnil(L);
        while (lua_next(L, -2)) {
            if (lua_type(L, -1) == LUA_TNUMBER) {
                /* part of free reference linked list, ignore */
                lua_pop(L, 1); /* table value */
                continue;
            }
            ProtoField f = checkProtoField(L, -1);
            if (strcmp(field_abbr, f->abbrev) == 0) {
                /* found! */
                lua_pop(L, 6);
                return f;
            }
            lua_pop(L, 1); /* table value */
        }
        lua_pop(L, 2); /* proto->fields and table value */
    }
    lua_pop(L, 1); /* protocols_table_ref */

    return NULL;
}

int pilua_deregister_protocols(lua_State* L)
{
    /* for each registered Proto protocol do... */
    lua_rawgeti(L, LUA_REGISTRYINDEX, protocols_table_ref);
    for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
        Proto proto;
        proto = checkProto(L, -1);

        if (proto->handle) {
            deregister_dissector(proto->loname);
        }
//        if (proto->prefs_module) {
//            Pref pref;
//            prefs_deregister_protocol(proto->hfid);
//            /* Preferences are unregistered, now free its memory via Pref__gc */
//            for (pref = proto->prefs.next; pref; pref = pref->next) {
//                int pref_ref = pref->ref;
//                pref->ref = LUA_NOREF;
//                luaL_unref(L, LUA_REGISTRYINDEX, pref_ref);
//            }
//        }
//        if (proto->expert_module) {
//            expert_deregister_protocol(proto->expert_module);
//        }
        proto_deregister_protocol(proto->name);

        /* for each registered ProtoField do... */
        lua_rawgeti(L, LUA_REGISTRYINDEX, proto->fields);
        for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
            if (lua_type(L, -1) == LUA_TNUMBER) {
                /* part of free reference linked list, ignore */
                continue;
            }
//            ProtoField f = checkProtoField(L, -1);

//            /* Memory ownership was previously transferred to epan in Proto_commit */
//            f->name = NULL;
//            f->abbrev = NULL;
//            f->vs = NULL;
//            f->blob = NULL;

//            f->hfid = -2; /* Deregister ProtoField, freed in ProtoField__gc */
        }
        lua_pop(L, 1);

        /* for each registered ProtoExpert do... */
//        lua_rawgeti(L, LUA_REGISTRYINDEX, proto->expert_info_table_ref);
//        for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
//            if (lua_type(L, -1) == LUA_TNUMBER) {
//                /* part of free reference linked list, ignore */
//                continue;
//            }
//            ProtoExpert pe = checkProtoExpert(L, -1);

//            /* Memory ownership was previously transferred to epan in Proto_commit */
//            pe->abbrev = NULL;
//            pe->text = NULL;

//            pe->ids.hf = -2; /* Deregister ProtoExpert, freed in ProtoExpert__gc */
//        }
//        lua_pop(L, 1);

        if (proto->hfa && proto->hfa->len) {
            proto_add_deregistered_data(g_array_free(proto->hfa,false));
        } else {
            g_array_free(proto->hfa,true);
        }

//        /* No need for deferred deletion of subtree indexes */
//        g_array_free(proto->etta,true);

//        if (proto->eia && proto->eia->len) {
//            proto_add_deregistered_data(g_array_free(proto->eia,false));
//        } else {
//            g_array_free(proto->eia,true);
//        }

        proto->hfid = -2; /* Deregister Proto, freed in Proto__gc */
    }

    lua_pop(L, 1); /* protocols_table_ref */

    return 0;
}

int Proto_commit(lua_State* L)
{
//printf("Proto_commit\n");
    lua_settop(L, 0);
    /* the following gets the table of registered Proto protocols and puts it on the stack (index=1) */
    lua_rawgeti(L, LUA_REGISTRYINDEX, protocols_table_ref);

    /* for each registered Proto protocol do... */
    for (lua_pushnil(L); lua_next(L, 1); lua_pop(L, 1)) {
        /* lua_next() pop'ed the nil, pushed a table entry key at index=2, with value at index=3.
           In our case, the key is the Proto's name, and the value is the Proto object.
           At next iteration, the value (Proto object) will be pop'ed due
           to lua_pop(L, 1), and when lua_next() returns 0 (no more table entries), it will have
           pop'ed the final key itself, leaving just the protocols_table_ref table on the stack.
         */
        Proto proto = checkProto(L, 3);
//        int*   ettp = NULL;

        if (proto->hfa) {
            /* This proto's ProtoFields were already registered. */
            continue;
        }

        proto->hfa  = g_array_new(true, true, sizeof(hf_register_info));
//        proto->etta = g_array_new(true, true, sizeof(int*));
//        proto->eia  = g_array_new(true, true, sizeof(ei_register_info));

//        ettp = &(proto->ett);
//        g_array_append_val(proto->etta, ettp);

        /* get the Lua table of ProtoFields, push it on the stack (index=3) */
        lua_rawgeti(L, LUA_REGISTRYINDEX, proto->fields);

        /* for each ProtoField in the Lua table do... */
        for (lua_pushnil(L); lua_next(L, 4); lua_pop(L, 1)) {
            if (lua_type(L, -1) == LUA_TNUMBER) {
                /* part of free reference linked list, ignore */
                continue;
            }
            ProtoField f = checkProtoField(L, 6);

            // XXX this will leak resources on error
            // If this continued and registered the field array, it wouldn't
            // leak. We could perhaps print a warning or even err after
            // registration.
            if (!Proto_append_ProtoField(proto, f)) {
                return luaL_error(L, "%s is already registered; fields can be registered only once", f->abbrev);
            }
        }

        /* register the proto fields */
        proto_register_field_array(proto->hfid,(hf_register_info*)(void*)proto->hfa->data,proto->hfa->len);
//        proto_register_subtree_array((int**)(void*)proto->etta->data,proto->etta->len);

        lua_pop(L, 1); /* pop the table of ProtoFields */

//        /* now do the same thing for expert fields */

//        /* get the Lua table of ProtoExperts, push it on the stack (index=2) */
//        lua_rawgeti(L, LUA_REGISTRYINDEX, proto->expert_info_table_ref);

//        /* for each ProtoExpert in the Lua table do... */
//        for (lua_pushnil(L); lua_next(L, 4); lua_pop(L, 1)) {
//            if (lua_type(L, -1) == LUA_TNUMBER) {
//                /* part of free reference linked list, ignore */
//                continue;
//            }
//            ProtoExpert e = checkProtoExpert(L, 6);
//            if (!Proto_append_ProtoExpert(proto, e)) {
//                return luaL_error(L,"%s is already registered; expert fields can be registered only once", e->abbrev);
//            }
//        }

//        expert_register_field_array(proto->expert_module, (ei_register_info*)(void*)proto->eia->data, proto->eia->len);

//        lua_pop(L, 1); /* pop the table of ProtoExperts */

        /* Proto object will be pop'ed by lua_pop(L, 1) in for statement */
    }

    lua_pop(L, 1); /* pop the protocols_table_ref */

//printf("Proto_commit end\n");
    return 0;
}
#if 0
static unsigned pilua_dissect_tcp_get_pdu_len(/*packet_info *pinfo, tvbuff_t *tvb,
                                              int offset,*/ void *data)
{
    /* WARNING: called from a TRY block, do not call luaL_error! */
    func_saver_t* fs = (func_saver_t*)data;
    lua_State* L = fs->state;
    int pdu_len = 0;

    lua_settop(L, 0);
    lua_rawgeti(L, LUA_REGISTRYINDEX, fs->get_len_ref);

//    if (lua_isfunction(L, 1)) {
//        push_Tvb(L, tvb);
//        push_Pinfo(L, pinfo);
//        lua_pushinteger(L, offset);

//        if  (lua_pcall(L, 3, 1, 0)) {
//            THROW_LUA_ERROR("Lua Error in dissect_tcp_pdus get_len_func: %s", lua_tostring(L,-1));
//        } else {
//            /* if the Lua dissector reported the consumed bytes, pass it to our caller */
//            if (lua_isnumber(L, -1)) {
//                /* we got the pdu_len */
//                pdu_len = pilua_toint(L, -1);
//                lua_pop(L, 1);
//            } else {
//                THROW_LUA_ERROR("Lua Error dissect_tcp_pdus: get_len_func did not return a Lua number of the PDU length");
//            }
//        }
//    } else {
//        REPORT_DISSECTOR_BUG("Lua Error in dissect_tcp_pdus: did not find the get_len_func dissector");
//    }

    return pdu_len;
}

static int pilua_dissect_tcp_dissector(/*tvbuff_t *tvb, packet_info *pinfo,
                                       proto_tree *tree,*/ void *data)
{
    /* WARNING: called from a TRY block, do not call luaL_error! */
    func_saver_t* fs = (func_saver_t*)data;
    lua_State* L = fs->state;
    int consumed_bytes = 0;

    lua_settop(L, 0);
    lua_rawgeti(L, LUA_REGISTRYINDEX, fs->dissect_ref);

//    if (lua_isfunction(L, 1)) {

//        push_Tvb(L, tvb);
//        push_Pinfo(L, pinfo);
//        /* XXX: not sure if it's kosher to just use the tree as the item */
//        push_TreeItem(L, tree, (proto_item*)tree);

//        if  ( lua_pcall(L, 3, 1, 0) ) {
//            THROW_LUA_ERROR("dissect_tcp_pdus dissect_func: %s", lua_tostring(L, -1));
//        } else {
//            /* if the Lua dissector reported the consumed bytes, pass it to our caller */
//            if (lua_isnumber(L, -1)) {
//                /* we got the consumed bytes or the missing bytes as a negative number */
//                consumed_bytes = pilua_toint(L, -1);
//                lua_pop(L, 1);
//            }
//        }
//    } else {
//        REPORT_DISSECTOR_BUG("dissect_tcp_pdus: did not find the dissect_func dissector");
//    }

    return consumed_bytes;
}

PILUA_FUNCTION pilua_dissect_tcp_pdus(lua_State* L)
{
    /* Make the TCP-layer invoke the given Lua dissection function for each
       PDU in the TCP segment, of the length returned by the given get_len_func
       function.

       This function is useful for protocols that run over TCP and that are
       either a fixed length always, or have a minimum size and have a length
       field encoded within that minimum portion that identifies their full
       length. For such protocols, their protocol dissector function can invoke
       this `dissect_tcp_pdus()` function to make it easier to handle dissecting
       their protocol's messages (i.e., their protocol data unit (PDU)). This
       function shouild not be used for protocols whose PDU length cannot be
       determined from a fixed minimum portion, such as HTTP or Telnet.
     */
#define PILUA_ARG_dissect_tcp_pdus_TVB              1 /* The Tvb buffer to dissect PDUs from. */
#define PILUA_ARG_dissect_tcp_pdus_TREE             2 /* The Tvb buffer to dissect PDUs from. */
#define PILUA_ARG_dissect_tcp_pdus_MIN_HEADER_SIZE  3 /* The number of bytes
                        in the fixed-length part of the PDU. */
#define PILUA_ARG_dissect_tcp_pdus_GET_LEN_FUNC     4 /* A Lua function that will be
                        called for each PDU, to determine the full length of the
                        PDU. The called function will be given (1) the `Tvb` object
                        of the whole `Tvb` (possibly reassembled), (2) the `Pinfo` object,
                        and (3) an offset number of the index of the first byte
                        of the PDU (i.e., its first header byte). The Lua function
                        must return a Lua number of the full length of the PDU. */
#define PILUA_ARG_dissect_tcp_pdus_DISSECT_FUNC     5 /* A Lua function that will be
                        called for each PDU, to dissect the PDU. The called
                        function will be given (1) the `Tvb` object of the PDU's
                        `Tvb` (possibly reassembled), (2) the `Pinfo` object,
                        and (3) the `TreeItem` object. The Lua function must
                        return a Lua number of the number of bytes read/handled,
                        which would typically be the `Tvb:len()`.*/
#define PILUA_OPTARG_dissect_tcp_pdus_DESEGMENT     6 /* Whether to reassemble PDUs
                        crossing TCP segment boundaries or not. (default=true) */
//    Tvb tvb = checkTvb(L, PILUA_ARG_dissect_tcp_pdus_TVB);
//    TreeItem ti = checkTreeItem(L, PILUA_ARG_dissect_tcp_pdus_TREE);
//    unsigned fixed_len = (unsigned)luaL_checkinteger(L, PILUA_ARG_dissect_tcp_pdus_MIN_HEADER_SIZE);
//    bool proto_desegment = pilua_optbool(L, PILUA_OPTARG_dissect_tcp_pdus_DESEGMENT, true);

    if (!lua_pinfo) {
        luaL_error(L, "dissect_tcp_pdus can only be invoked while in a dissect function");
        return 0;
    }

//    if (lua_isfunction(L, PILUA_ARG_dissect_tcp_pdus_GET_LEN_FUNC) &&
//        lua_isfunction(L, PILUA_ARG_dissect_tcp_pdus_DISSECT_FUNC))
//    {
//        /* save the Lua functions so that we can call them later */
//        func_saver_t* fs = malloc(sizeof(func_saver_t));

//        lua_settop(L, PILUA_ARG_dissect_tcp_pdus_DISSECT_FUNC);

//        fs->state = L;
//        /* the following pops the top function and sets a ref to it in the registry */
//        fs->dissect_ref = luaL_ref(L, LUA_REGISTRYINDEX);
//        fs->get_len_ref = luaL_ref(L, LUA_REGISTRYINDEX);

//        /* save the passed-in function refs, so Lua's garbage collector won't
//           destroy them before they get invoked */
//        g_ptr_array_add(outstanding_FuncSavers, fs);

////        WRAP_NON_LUA_EXCEPTIONS(
////            tcp_dissect_pdus(tvb->pi_tvb, lua_pinfo, ti->tree, proto_desegment,
////                             fixed_len, pilua_dissect_tcp_get_pdu_len,
////                             pilua_dissect_tcp_dissector, (void*)fs);
////        )
//    } else {
//        luaL_error(L, "The third and fourth arguments need to be Lua functions");
//    }
    return 0;
}
#endif
